{
 "metadata": {
  "name": "",
  "signature": "sha256:4c3cf1f40ab9dc237c8af5da2535307b8c31eebdcf0182a22bf6f16a434c6436"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "heading",
     "level": 1,
     "metadata": {},
     "source": [
      "Rotated and shifted problems"
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "In this tutorial we will learn how to use meta-problems. These are optimization problems that transform somehow another optimization problem. In particular we will have a look to the rotated and shifted meta-problems. Let us start creating a shifted problem."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from PyGMO import problem\n",
      "prob = problem.ackley(5)\n",
      "shifted_prob1 = problem.shifted(problem=prob)\n",
      "shifted_prob2 = problem.shifted(problem=prob, shift=15)\n",
      "shifted_prob3 = problem.shifted(problem=prob, shift=[23, -12.2, 22, 33, 5.3])"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "We have used three different constructors to instantiate the new problem with a random shift vector (shifted_prob1), with a uniform shift vector (shifted_prob2) and with a fully defined shift vector (shifted_prob3). In all cases we may extract the shift vector using the corresponding attribute"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "print(shifted_prob1.shift_vector)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "We may now check that such a shift does not change the performance of a given algorithm. We choose, for this tutorial Improved Harmony Search, but you can try changing it to test others."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from PyGMO import algorithm, population\n",
      "import matplotlib.pyplot as plt\n",
      "\n",
      "def compare_problems(prob, prob_meta):\n",
      "    # Evolve 100 populations for each problem and compare\n",
      "    # the champions' objective function distribution\n",
      "    l = []\n",
      "    algo = algorithm.ihs(1000);\n",
      "    for _ in range(100):\n",
      "        pop = population(prob, 20)\n",
      "        for i in range(15):\n",
      "            pop = algo.evolve(pop)\n",
      "        l.append(pop.champion.f[0])\n",
      "    l_meta = []\n",
      "    for _ in range(100):\n",
      "        pop = population(prob_meta, 20)\n",
      "        for _ in range(15):\n",
      "            pop = algo.evolve(pop)\n",
      "        l_meta.append(pop.champion.f[0])\n",
      "        \n",
      "    plt.ylabel(\"Objective function\")\n",
      "    plt.yscale('log')\n",
      "    plt.xticks([0, 1], ['Original', 'Meta problem'])\n",
      "    plt.boxplot([l, l_meta])\n",
      "    plt.show()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Call the function for shifted problem\n",
      "compare_problems(prob, shifted_prob1)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "We can clearly see how the algorithm ihs does not change its performances when the search space is shifted.\n",
      "\n",
      "We now repeat the same procedure for a rotated problem. Also in the case of the rotated problem the kwarg rotation allows to pass a rotation matrix directly, otherwise a random orthonormal matrix will be generated and can be extracted by the problem.rotation attribute."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "prob = problem.ackley(5)\n",
      "rotated_prob = problem.rotated(problem=prob)\n",
      "compare_problems(prob, rotated_prob)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Which clearly indicates how the rotation affects negatively the algorithm performance.\n",
      "\n",
      "Note that meta-problems can be nested together, so it is perfectly valid to have"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "prob = problem.ackley(5)\n",
      "new_prob = problem.rotated(problem.shifted(problem.rotated(prob)))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "To make sure one can reconstruct the original problem, the transformations applied are logged in the problem `__repr__` method"
     ]
    },
    {
     "cell_type": "heading",
     "level": 1,
     "metadata": {},
     "source": [
      "The noisy meta-problems"
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "The noisy meta-problem introduces noise into regular PyGMO problem, rendering them stochastic. More specifically, the observed fitness and constraint vectors from such problems have been corrupted by noises. Currently, two types of noise distributions are supported, namely the normal distribution and uniform distribution."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from PyGMO import *\n",
      "prob = problem.ackley(1)\n",
      "prob_noisy_normal = problem.noisy(prob, trials=1, param_first=0.0, param_second=3.0, noise_type=problem.noisy.noise_distribution.NORMAL)\n",
      "prob_noisy_uniform = problem.noisy(prob, trials=1, param_first=-3.0, param_second=3.0, noise_type=problem.noisy.noise_distribution.UNIFORM)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "The construction parameters control the following aspects of the noisy transform:\n",
      "\n",
      "* **Noise distribution**: The Gaussian noise is characterized by a mean of ``param_first`` and a standard deviation of ``param_second``, while the uniformly noise is uniformly distributed between ``param_first`` and ``param_second``.\n",
      "* **Internal averaging**: The noisy meta-problem provides an internal mechanism to perform averaging via repeated evaluation before reporting the fitness / constraint vectors, at the expense of increase computational budget. The number of samples to average over is controlled by the parameter ``trials``.\n",
      "\n",
      "The fitness landscape (or more precisely the distribution of fitness values at each decision value) of a noisy one-dimensional Ackley problem is visualized in the following figures. The solid line is the fitness in case of a noise-less problem."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "import numpy as np\n",
      "import matplotlib.pyplot as plt\n",
      "\n",
      "# Set up plotting canvas\n",
      "fig = plt.figure(figsize=(12, 4))\n",
      "ax1 = fig.add_subplot(121)\n",
      "ax2 = fig.add_subplot(122)\n",
      "for ax in [ax1, ax2]:\n",
      "    ax.set_ylim(-10, 30)\n",
      "    ax.set_xlim(-15, 30)\n",
      "ax1.set_title(\"1D Ackley [Noisy NORMAL(0.0, 3.0)]\")\n",
      "ax2.set_title(\"1D Ackley [Noisy UNIFORM(-3.0, 3.0)]\")\n",
      "\n",
      "# Plot the non-noised reference\n",
      "xs = np.linspace(-15, 30, 200)\n",
      "ys = [prob.objfun([xsi, ]) for xsi in xs]\n",
      "ax1.plot(xs, ys, c='k')\n",
      "ax2.plot(xs, ys, c='k')\n",
      "\n",
      "n_trials = 200\n",
      "# Plot the normal-noised function\n",
      "import random\n",
      "for i in range(n_trials):\n",
      "    seed = random.randint(1, 100000)\n",
      "    prob_noisy_normal.seed = seed\n",
      "    ys_noised = [prob_noisy_normal.objfun([xsi, ]) for xsi in xs]\n",
      "    ax1.scatter(xs, ys_noised, s=1, c='k', alpha=0.02)\n",
      "\n",
      "# Plot the uniform-noised function\n",
      "for i in range(n_trials):\n",
      "    seed = random.randint(1, 100000)\n",
      "    prob_noisy_uniform.seed = seed\n",
      "    ys_noised = [prob_noisy_uniform.objfun([xsi, ]) for xsi in xs]\n",
      "    ax2.scatter(xs, ys_noised, s=1, c='k', alpha=0.02)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "The transformed problem becomes stochastic. It can be solved by optimizers capable of handling stochastic problems, for example ``pso_gen``. The quality of a solution can be assessed by the noise-less version of the problem, serving as the ground truth information.\n",
      "\n",
      "As some examples, let\u2019s apply ``pso_gen`` to different version of noisy problems. Generally, the larger the magnitude of noise, the harder the problem becomes. Increasing the ``trials`` parameter reduces the adversarial effect of noise."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# NOTE: This script can take up to 1 minute to finish\n",
      "\n",
      "# Set up plotting canvas\n",
      "fig = plt.figure(figsize=(12, 4))\n",
      "ax1 = fig.add_subplot(121)\n",
      "ax2 = fig.add_subplot(122)\n",
      "ax1.set_title(\"Noised (NORMAL(0, x), trials=1) Ackley(D=10)\")\n",
      "ax1.set_xlabel(\"Standard deviation of noise\")\n",
      "ax1.set_ylabel(\"Objective function value\")\n",
      "ax2.set_xlabel(\"Number of trials\")\n",
      "ax2.set_ylabel(\"Objective function value\")\n",
      "ax2.set_title(\"Noised (NORMAL(0, 0.3), trials=x) Ackley(D=10)\")\n",
      "\n",
      "# Set up the problem for varying STD test\n",
      "prob = problem.ackley(10)\n",
      "l = []\n",
      "for i in range(11):\n",
      "    prob_noisy_normal = problem.noisy(prob, trials=1, param_first=0.0,\n",
      "                                      param_second=float(i) / 10, \n",
      "                                      noise_type=problem.noisy.noise_distribution.NORMAL)\n",
      "    d = []\n",
      "    for j in range(30):\n",
      "        seed = random.randint(1, 100000)\n",
      "        prob_noisy_normal.seed = seed\n",
      "        alg = algorithm.pso_gen(gen=100)\n",
      "        pop = population(prob_noisy_normal, 40)\n",
      "        pop = alg.evolve(pop)\n",
      "        d.append(pop.champion.f[0])\n",
      "    l.append(d)\n",
      "ax1.boxplot(l)\n",
      "ax1.set_xticklabels([\"{:.1f}\".format(float(i)/10) for i in range(11)])\n",
      "\n",
      "# Set up the varying trials parameter test\n",
      "l = []\n",
      "for tr in [1, 5, 10, 20, 40]:\n",
      "    prob_noisy_normal = problem.noisy(prob, trials=tr, param_first=0.0,\n",
      "                                      param_second=0.3, \n",
      "                                      noise_type=problem.noisy.noise_distribution.NORMAL)\n",
      "    d = []\n",
      "    for j in range(30):\n",
      "        seed = random.randint(1, 100000)\n",
      "        prob_noisy_normal.seed = seed\n",
      "        alg = algorithm.pso_gen(gen=100)\n",
      "        pop = population(prob_noisy_normal, 40)\n",
      "        pop = alg.evolve(pop)\n",
      "        d.append(pop.champion.f[0])\n",
      "    l.append(d)\n",
      "ax2.boxplot(l)\n",
      "ax2.set_xticklabels([\"{:d}\".format(i) for i in [1, 5, 10, 20, 40]])\n",
      "\n",
      "plt.show()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "heading",
     "level": 1,
     "metadata": {},
     "source": [
      "Decomposition"
     ]
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "In this tutorial we will learn how to use the decompose meta-problems to solve a multi-objective problem. The decompose meta-problem transforms a multi-objective problem into a single-objective one having as fitness function a convex combination (defined by a weight vector) of the original objectives. Let us start creating a decomposed problem from a multi-objective one."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from PyGMO import problem\n",
      "orig_prob = problem.zdt(1, 10)\n",
      "prob = problem.decompose(problem=orig_prob, weights=[0.5, 0.5])"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "In this way the 2 objectives of the original problem are equally weighted. If we don\u2019t define the weight vector, then it is randomly generated.\n",
      "\n",
      "We then proceed by solving the new decomposed problem using a single-objective optimization algorithm, and see how the fitness of the new problem is equal to the average of the original 2 objectives"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "alg = algorithm.jde(50)\n",
      "pop = population(prob, 200)\n",
      "for i in xrange(5):\n",
      "    pop = alg.evolve(pop)\n",
      "    print(\"Generation {}\".format(i))\n",
      "    print(\"Distance from Pareto Front (p-distance): {}\".format(orig_prob.p_distance(pop)))\n",
      "    print(\"Original fitness: {}\".format(orig_prob.objfun(pop.champion.x)))\n",
      "    print(\"Decomposed fitness: {}\\n\".format(pop.champion.f))"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    }
   ],
   "metadata": {}
  }
 ]
}